package com.rengwuxian.materialedittext;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.AppCompatMultiAutoCompleteTextView;
import android.text.Editable;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;

import com.nineoldandroids.animation.ArgbEvaluator;
import com.nineoldandroids.animation.ObjectAnimator;
import com.rengwuxian.materialedittext.validation.METLengthChecker;
import com.rengwuxian.materialedittext.validation.METValidator;
import com.yzx.lifeassistants.R;

/**
 * Created by rengwuxian on 2015/1/8.
 */
@SuppressLint("NewApi")
public class MaterialMultiAutoCompleteTextView extends
		AppCompatMultiAutoCompleteTextView {

	@IntDef({ FLOATING_LABEL_NONE, FLOATING_LABEL_NORMAL,
			FLOATING_LABEL_HIGHLIGHT })
	public @interface FloatingLabelType {
	}

	public static final int FLOATING_LABEL_NONE = 0;
	public static final int FLOATING_LABEL_NORMAL = 1;
	public static final int FLOATING_LABEL_HIGHLIGHT = 2;

	/**
	 * the spacing between the main text and the inner top padding.
	 */
	private int extraPaddingTop;

	/**
	 * the spacing between the main text and the inner bottom padding.
	 */
	private int extraPaddingBottom;

	/**
	 * the extra spacing between the main text and the left, actually for the
	 * left icon.
	 */
	private int extraPaddingLeft;

	/**
	 * the extra spacing between the main text and the right, actually for the
	 * right icon.
	 */
	private int extraPaddingRight;

	/**
	 * the floating label's text size.
	 */
	private int floatingLabelTextSize;

	/**
	 * the floating label's text color.
	 */
	private int floatingLabelTextColor;

	/**
	 * the bottom texts' size.
	 */
	private int bottomTextSize;

	/**
	 * the spacing between the main text and the floating label.
	 */
	private int floatingLabelPadding;

	/**
	 * the spacing between the main text and the bottom components (bottom
	 * ellipsis, helper/error text, characters counter).
	 */
	private int bottomSpacing;

	/**
	 * whether the floating label should be shown. default is false.
	 */
	private boolean floatingLabelEnabled;

	/**
	 * whether to highlight the floating label's text color when focused (with
	 * the main color). default is true.
	 */
	private boolean highlightFloatingLabel;

	/**
	 * the base color of the line and the texts. default is black.
	 */
	private int baseColor;

	/**
	 * inner top padding
	 */
	private int innerPaddingTop;

	/**
	 * inner bottom padding
	 */
	private int innerPaddingBottom;

	/**
	 * inner left padding
	 */
	private int innerPaddingLeft;

	/**
	 * inner right padding
	 */
	private int innerPaddingRight;

	/**
	 * the underline's highlight color, and the highlight color of the floating
	 * label if app:highlightFloatingLabel is set true in the xml. default is
	 * black(when app:darkTheme is false) or white(when app:darkTheme is true)
	 */
	private int primaryColor;

	/**
	 * the color for when something is wrong.(e.g. exceeding max characters)
	 */
	private int errorColor;

	/**
	 * min characters count limit. 0 means no limit. default is 0. NOTE: the
	 * character counter will increase the View's height.
	 */
	private int minCharacters;

	/**
	 * max characters count limit. 0 means no limit. default is 0. NOTE: the
	 * character counter will increase the View's height.
	 */
	private int maxCharacters;

	/**
	 * whether to show the bottom ellipsis in singleLine mode. default is false.
	 * NOTE: the bottom ellipsis will increase the View's height.
	 */
	private boolean singleLineEllipsis;

	/**
	 * Always show the floating label, instead of animating it in/out. False by
	 * default.
	 */
	private boolean floatingLabelAlwaysShown;

	/**
	 * Always show the helper text, no matter if the edit text is focused. False
	 * by default.
	 */
	private boolean helperTextAlwaysShown;

	/**
	 * bottom ellipsis's height
	 */
	private int bottomEllipsisSize;

	/**
	 * min bottom lines count.
	 */
	private int minBottomLines;

	/**
	 * reserved bottom text lines count, no matter if there is some helper/error
	 * text.
	 */
	private int minBottomTextLines;

	/**
	 * real-time bottom lines count. used for bottom extending/collapsing
	 * animation.
	 */
	private float currentBottomLines;

	/**
	 * bottom lines count.
	 */
	private float bottomLines;

	/**
	 * Helper text at the bottom
	 */
	private String helperText;

	/**
	 * Helper text color
	 */
	private int helperTextColor = -1;

	/**
	 * error text for manually invoked {@link #setError(CharSequence)}
	 */
	private String tempErrorText;

	/**
	 * animation fraction of the floating label (0 as totally hidden).
	 */
	private float floatingLabelFraction;

	/**
	 * whether the floating label is being shown.
	 */
	private boolean floatingLabelShown;

	/**
	 * the floating label's focusFraction
	 */
	private float focusFraction;

	/**
	 * The font used for the accent texts (floating label, error/helper text,
	 * character counter, etc.)
	 */
	private Typeface accentTypeface;

	/**
	 * The font used on the view (EditText content)
	 */
	private Typeface typeface;

	/**
	 * Text for the floatLabel if different from the hint
	 */
	private CharSequence floatingLabelText;

	/**
	 * Whether or not to show the underline. Shown by default
	 */
	private boolean hideUnderline;

	/**
	 * Underline's color
	 */
	private int underlineColor;

	/**
	 * Whether to validate as soon as the text has changed. False by default
	 */
	private boolean autoValidate;

	/**
	 * Whether the characters count is valid
	 */
	private boolean charactersCountValid;

	/**
	 * Whether use animation to show/hide the floating label.
	 */
	private boolean floatingLabelAnimating;

	/**
	 * Whether check the characters count at the beginning it's shown.
	 */
	private boolean checkCharactersCountAtBeginning;

	/**
	 * Left Icon
	 */
	private Bitmap[] iconLeftBitmaps;

	/**
	 * Right Icon
	 */
	private Bitmap[] iconRightBitmaps;

	/**
	 * Clear Button
	 */
	private Bitmap[] clearButtonBitmaps;

	/**
	 * Auto validate when focus lost.
	 */
	private boolean validateOnFocusLost;

	private boolean showClearButton;
	private boolean firstShown;
	private int iconSize;
	private int iconOuterWidth;
	private int iconOuterHeight;
	private int iconPadding;
	private boolean clearButtonTouched;
	private boolean clearButtonClicking;
	private ColorStateList textColorStateList;
	private ColorStateList textColorHintStateList;
	private ArgbEvaluator focusEvaluator = new ArgbEvaluator();
	Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
	TextPaint textPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
	StaticLayout textLayout;
	ObjectAnimator labelAnimator;
	ObjectAnimator labelFocusAnimator;
	ObjectAnimator bottomLinesAnimator;
	OnFocusChangeListener innerFocusChangeListener;
	OnFocusChangeListener outerFocusChangeListener;
	private List<METValidator> validators;
	private METLengthChecker lengthChecker;

	public MaterialMultiAutoCompleteTextView(Context context) {
		super(context);
		init(context, null);
	}

	public MaterialMultiAutoCompleteTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init(context, attrs);
	}

	@TargetApi(Build.VERSION_CODES.LOLLIPOP)
	public MaterialMultiAutoCompleteTextView(Context context,
			AttributeSet attrs, int style) {
		super(context, attrs, style);
		init(context, attrs);
	}

	private void init(Context context, AttributeSet attrs) {
		iconSize = getPixel(32);
		iconOuterWidth = getPixel(48);
		iconOuterHeight = getPixel(32);

		bottomSpacing = getResources().getDimensionPixelSize(
				R.dimen.inner_components_spacing);
		bottomEllipsisSize = getResources().getDimensionPixelSize(
				R.dimen.bottom_ellipsis_height);

		// default baseColor is black
		int defaultBaseColor = Color.BLACK;

		TypedArray typedArray = context.obtainStyledAttributes(attrs,
				R.styleable.MaterialEditText);
		textColorStateList = typedArray
				.getColorStateList(R.styleable.MaterialEditText_met_textColor);
		textColorHintStateList = typedArray
				.getColorStateList(R.styleable.MaterialEditText_met_textColorHint);
		baseColor = typedArray.getColor(
				R.styleable.MaterialEditText_met_baseColor, defaultBaseColor);

		// retrieve the default primaryColor
		int defaultPrimaryColor;
		TypedValue primaryColorTypedValue = new TypedValue();
		try {
			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
				context.getTheme().resolveAttribute(
						android.R.attr.colorPrimary, primaryColorTypedValue,
						true);
				defaultPrimaryColor = primaryColorTypedValue.data;
			} else {
				throw new RuntimeException("SDK_INT less than LOLLIPOP");
			}
		} catch (Exception e) {
			try {
				int colorPrimaryId = getResources().getIdentifier(
						"colorPrimary", "attr", getContext().getPackageName());
				if (colorPrimaryId != 0) {
					context.getTheme().resolveAttribute(colorPrimaryId,
							primaryColorTypedValue, true);
					defaultPrimaryColor = primaryColorTypedValue.data;
				} else {
					throw new RuntimeException("colorPrimary not found");
				}
			} catch (Exception e1) {
				defaultPrimaryColor = baseColor;
			}
		}

		primaryColor = typedArray.getColor(
				R.styleable.MaterialEditText_met_primaryColor,
				defaultPrimaryColor);
		setFloatingLabelInternal(typedArray.getInt(
				R.styleable.MaterialEditText_met_floatingLabel, 0));
		errorColor = typedArray.getColor(
				R.styleable.MaterialEditText_met_errorColor,
				Color.parseColor("#e7492E"));
		minCharacters = typedArray.getInt(
				R.styleable.MaterialEditText_met_minCharacters, 0);
		maxCharacters = typedArray.getInt(
				R.styleable.MaterialEditText_met_maxCharacters, 0);
		singleLineEllipsis = typedArray.getBoolean(
				R.styleable.MaterialEditText_met_singleLineEllipsis, false);
		helperText = typedArray
				.getString(R.styleable.MaterialEditText_met_helperText);
		helperTextColor = typedArray.getColor(
				R.styleable.MaterialEditText_met_helperTextColor, -1);
		minBottomTextLines = typedArray.getInt(
				R.styleable.MaterialEditText_met_minBottomTextLines, 0);
		String fontPathForAccent = typedArray
				.getString(R.styleable.MaterialEditText_met_accentTypeface);
		if (fontPathForAccent != null && !isInEditMode()) {
			accentTypeface = getCustomTypeface(fontPathForAccent);
			textPaint.setTypeface(accentTypeface);
		}
		String fontPathForView = typedArray
				.getString(R.styleable.MaterialEditText_met_typeface);
		if (fontPathForView != null && !isInEditMode()) {
			typeface = getCustomTypeface(fontPathForView);
			setTypeface(typeface);
		}
		floatingLabelText = typedArray
				.getString(R.styleable.MaterialEditText_met_floatingLabelText);
		if (floatingLabelText == null) {
			floatingLabelText = getHint();
		}
		floatingLabelPadding = typedArray.getDimensionPixelSize(
				R.styleable.MaterialEditText_met_floatingLabelPadding,
				bottomSpacing);
		floatingLabelTextSize = typedArray.getDimensionPixelSize(
				R.styleable.MaterialEditText_met_floatingLabelTextSize,
				getResources().getDimensionPixelSize(
						R.dimen.floating_label_text_size));
		floatingLabelTextColor = typedArray.getColor(
				R.styleable.MaterialEditText_met_floatingLabelTextColor, -1);
		floatingLabelAnimating = typedArray.getBoolean(
				R.styleable.MaterialEditText_met_floatingLabelAnimating, true);
		bottomTextSize = typedArray.getDimensionPixelSize(
				R.styleable.MaterialEditText_met_bottomTextSize, getResources()
						.getDimensionPixelSize(R.dimen.bottom_text_size));
		hideUnderline = typedArray.getBoolean(
				R.styleable.MaterialEditText_met_hideUnderline, false);
		underlineColor = typedArray.getColor(
				R.styleable.MaterialEditText_met_underlineColor, -1);
		autoValidate = typedArray.getBoolean(
				R.styleable.MaterialEditText_met_autoValidate, false);
		iconLeftBitmaps = generateIconBitmaps(typedArray.getResourceId(
				R.styleable.MaterialEditText_met_iconLeft, -1));
		iconRightBitmaps = generateIconBitmaps(typedArray.getResourceId(
				R.styleable.MaterialEditText_met_iconRight, -1));
		showClearButton = typedArray.getBoolean(
				R.styleable.MaterialEditText_met_clearButton, false);
		clearButtonBitmaps = generateIconBitmaps(R.drawable.met_ic_clear);
		iconPadding = typedArray.getDimensionPixelSize(
				R.styleable.MaterialEditText_met_iconPadding, getPixel(16));
		floatingLabelAlwaysShown = typedArray.getBoolean(
				R.styleable.MaterialEditText_met_floatingLabelAlwaysShown,
				false);
		helperTextAlwaysShown = typedArray.getBoolean(
				R.styleable.MaterialEditText_met_helperTextAlwaysShown, false);
		validateOnFocusLost = typedArray.getBoolean(
				R.styleable.MaterialEditText_met_validateOnFocusLost, false);
		checkCharactersCountAtBeginning = typedArray
				.getBoolean(
						R.styleable.MaterialEditText_met_checkCharactersCountAtBeginning,
						true);
		typedArray.recycle();

		int[] paddings = new int[] { android.R.attr.padding, // 0
				android.R.attr.paddingLeft, // 1
				android.R.attr.paddingTop, // 2
				android.R.attr.paddingRight, // 3
				android.R.attr.paddingBottom // 4
		};
		TypedArray paddingsTypedArray = context.obtainStyledAttributes(attrs,
				paddings);
		int padding = paddingsTypedArray.getDimensionPixelSize(0, 0);
		innerPaddingLeft = paddingsTypedArray.getDimensionPixelSize(1, padding);
		innerPaddingTop = paddingsTypedArray.getDimensionPixelSize(2, padding);
		innerPaddingRight = paddingsTypedArray
				.getDimensionPixelSize(3, padding);
		innerPaddingBottom = paddingsTypedArray.getDimensionPixelSize(4,
				padding);
		paddingsTypedArray.recycle();

		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
			setBackground(null);
		} else {
			setBackgroundDrawable(null);
		}
		if (singleLineEllipsis) {
			TransformationMethod transformationMethod = getTransformationMethod();
			setSingleLine();
			setTransformationMethod(transformationMethod);
		}
		initMinBottomLines();
		initPadding();
		initText();
		initFloatingLabel();
		initTextWatcher();
		checkCharactersCount();
	}

	private void initText() {
		if (!TextUtils.isEmpty(getText())) {
			CharSequence text = getText();
			setText(null);
			resetHintTextColor();
			setText(text);
			setSelection(text.length());
			floatingLabelFraction = 1;
			floatingLabelShown = true;
		} else {
			resetHintTextColor();
		}
		resetTextColor();
	}

	private void initTextWatcher() {
		addTextChangedListener(new TextWatcher() {
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count,
					int after) {
			}

			@Override
			public void onTextChanged(CharSequence s, int start, int before,
					int count) {
			}

			@Override
			public void afterTextChanged(Editable s) {
				checkCharactersCount();
				if (autoValidate) {
					validate();
				} else {
					setError(null);
				}
				postInvalidate();
			}
		});
	}

	private Typeface getCustomTypeface(@NonNull String fontPath) {
		return Typeface.createFromAsset(getContext().getAssets(), fontPath);
	}

	public void setIconLeft(@DrawableRes int res) {
		iconLeftBitmaps = generateIconBitmaps(res);
		initPadding();
	}

	public void setIconLeft(Drawable drawable) {
		iconLeftBitmaps = generateIconBitmaps(drawable);
		initPadding();
	}

	public void setIconLeft(Bitmap bitmap) {
		iconLeftBitmaps = generateIconBitmaps(bitmap);
		initPadding();
	}

	public void setIconRight(@DrawableRes int res) {
		iconRightBitmaps = generateIconBitmaps(res);
		initPadding();
	}

	public void setIconRight(Drawable drawable) {
		iconRightBitmaps = generateIconBitmaps(drawable);
		initPadding();
	}

	public void setIconRight(Bitmap bitmap) {
		iconRightBitmaps = generateIconBitmaps(bitmap);
		initPadding();
	}

	public boolean isShowClearButton() {
		return showClearButton;
	}

	public void setShowClearButton(boolean show) {
		showClearButton = show;
		correctPaddings();
	}

	private Bitmap[] generateIconBitmaps(@DrawableRes int origin) {
		if (origin == -1) {
			return null;
		}
		BitmapFactory.Options options = new BitmapFactory.Options();
		options.inJustDecodeBounds = true;
		BitmapFactory.decodeResource(getResources(), origin, options);
		int size = Math.max(options.outWidth, options.outHeight);
		options.inSampleSize = size > iconSize ? size / iconSize : 1;
		options.inJustDecodeBounds = false;
		return generateIconBitmaps(BitmapFactory.decodeResource(getResources(),
				origin, options));
	}

	private Bitmap[] generateIconBitmaps(Drawable drawable) {
		if (drawable == null)
			return null;
		Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
				drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
		Canvas canvas = new Canvas(bitmap);
		drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
		drawable.draw(canvas);
		return generateIconBitmaps(Bitmap.createScaledBitmap(bitmap, iconSize,
				iconSize, false));
	}

	private Bitmap[] generateIconBitmaps(Bitmap origin) {
		if (origin == null) {
			return null;
		}
		Bitmap[] iconBitmaps = new Bitmap[4];
		origin = scaleIcon(origin);
		iconBitmaps[0] = origin.copy(Bitmap.Config.ARGB_8888, true);
		Canvas canvas = new Canvas(iconBitmaps[0]);
		canvas.drawColor(baseColor & 0x00ffffff
				| (Colors.isLight(baseColor) ? 0xff000000 : 0x8a000000),
				PorterDuff.Mode.SRC_IN);
		iconBitmaps[1] = origin.copy(Bitmap.Config.ARGB_8888, true);
		canvas = new Canvas(iconBitmaps[1]);
		canvas.drawColor(primaryColor, PorterDuff.Mode.SRC_IN);
		iconBitmaps[2] = origin.copy(Bitmap.Config.ARGB_8888, true);
		canvas = new Canvas(iconBitmaps[2]);
		canvas.drawColor(baseColor & 0x00ffffff
				| (Colors.isLight(baseColor) ? 0x4c000000 : 0x42000000),
				PorterDuff.Mode.SRC_IN);
		iconBitmaps[3] = origin.copy(Bitmap.Config.ARGB_8888, true);
		canvas = new Canvas(iconBitmaps[3]);
		canvas.drawColor(errorColor, PorterDuff.Mode.SRC_IN);
		return iconBitmaps;
	}

	private Bitmap scaleIcon(Bitmap origin) {
		int width = origin.getWidth();
		int height = origin.getHeight();
		int size = Math.max(width, height);
		if (size == iconSize) {
			return origin;
		} else if (size > iconSize) {
			int scaledWidth;
			int scaledHeight;
			if (width > iconSize) {
				scaledWidth = iconSize;
				scaledHeight = (int) (iconSize * ((float) height / width));
			} else {
				scaledHeight = iconSize;
				scaledWidth = (int) (iconSize * ((float) width / height));
			}
			return Bitmap.createScaledBitmap(origin, scaledWidth, scaledHeight,
					false);
		} else {
			return origin;
		}
	}

	public float getFloatingLabelFraction() {
		return floatingLabelFraction;
	}

	public void setFloatingLabelFraction(float floatingLabelFraction) {
		this.floatingLabelFraction = floatingLabelFraction;
		invalidate();
	}

	public float getFocusFraction() {
		return focusFraction;
	}

	public void setFocusFraction(float focusFraction) {
		this.focusFraction = focusFraction;
		invalidate();
	}

	public float getCurrentBottomLines() {
		return currentBottomLines;
	}

	public void setCurrentBottomLines(float currentBottomLines) {
		this.currentBottomLines = currentBottomLines;
		initPadding();
	}

	public boolean isFloatingLabelAlwaysShown() {
		return floatingLabelAlwaysShown;
	}

	public void setFloatingLabelAlwaysShown(boolean floatingLabelAlwaysShown) {
		this.floatingLabelAlwaysShown = floatingLabelAlwaysShown;
		invalidate();
	}

	public boolean isHelperTextAlwaysShown() {
		return helperTextAlwaysShown;
	}

	public void setHelperTextAlwaysShown(boolean helperTextAlwaysShown) {
		this.helperTextAlwaysShown = helperTextAlwaysShown;
		invalidate();
	}

	@Nullable
	public Typeface getAccentTypeface() {
		return accentTypeface;
	}

	/**
	 * Set typeface used for the accent texts (floating label, error/helper
	 * text, character counter, etc.)
	 */
	public void setAccentTypeface(Typeface accentTypeface) {
		this.accentTypeface = accentTypeface;
		this.textPaint.setTypeface(accentTypeface);
		postInvalidate();
	}

	public boolean isHideUnderline() {
		return hideUnderline;
	}

	/**
	 * Set whether or not to hide the underline (shown by default).
	 * <p/>
	 * The positions of text below will be adjusted accordingly (error/helper
	 * text, character counter, ellipses, etc.)
	 * <p/>
	 * NOTE: You probably don't want to hide this if you have any subtext
	 * features of this enabled, as it can look weird to not have a dividing
	 * line between them.
	 */
	public void setHideUnderline(boolean hideUnderline) {
		this.hideUnderline = hideUnderline;
		initPadding();
		postInvalidate();
	}

	/**
	 * get the color of the underline for normal state
	 */
	public int getUnderlineColor() {
		return underlineColor;
	}

	/**
	 * Set the color of the underline for normal state
	 * 
	 * @param color
	 */
	public void setUnderlineColor(int color) {
		this.underlineColor = color;
		postInvalidate();
	}

	public CharSequence getFloatingLabelText() {
		return floatingLabelText;
	}

	/**
	 * Set the floating label text.
	 * <p/>
	 * Pass null to force fallback to use hint's value.
	 * 
	 * @param floatingLabelText
	 */
	public void setFloatingLabelText(@Nullable CharSequence floatingLabelText) {
		this.floatingLabelText = floatingLabelText == null ? getHint()
				: floatingLabelText;
		postInvalidate();
	}

	public int getFloatingLabelTextSize() {
		return floatingLabelTextSize;
	}

	public void setFloatingLabelTextSize(int size) {
		floatingLabelTextSize = size;
		initPadding();
	}

	public int getFloatingLabelTextColor() {
		return floatingLabelTextColor;
	}

	public void setFloatingLabelTextColor(int color) {
		this.floatingLabelTextColor = color;
		postInvalidate();
	}

	public int getBottomTextSize() {
		return bottomTextSize;
	}

	public void setBottomTextSize(int size) {
		bottomTextSize = size;
		initPadding();
	}

	private int getPixel(int dp) {
		return Density.dp2px(getContext(), dp);
	}

	private void initPadding() {
		extraPaddingTop = floatingLabelEnabled ? floatingLabelTextSize
				+ floatingLabelPadding : floatingLabelPadding;
		textPaint.setTextSize(bottomTextSize);
		Paint.FontMetrics textMetrics = textPaint.getFontMetrics();
		extraPaddingBottom = (int) ((textMetrics.descent - textMetrics.ascent) * currentBottomLines)
				+ (hideUnderline ? bottomSpacing : bottomSpacing * 2);
		extraPaddingLeft = iconLeftBitmaps == null ? 0
				: (iconOuterWidth + iconPadding);
		extraPaddingRight = iconRightBitmaps == null ? 0
				: (iconOuterWidth + iconPadding);
		correctPaddings();
	}

	/**
	 * calculate {@link #minBottomLines}
	 */
	private void initMinBottomLines() {
		boolean extendBottom = minCharacters > 0 || maxCharacters > 0
				|| singleLineEllipsis || tempErrorText != null
				|| helperText != null;
		currentBottomLines = minBottomLines = minBottomTextLines > 0 ? minBottomTextLines
				: extendBottom ? 1 : 0;
	}

	/**
	 * use {@link #setPaddings(int, int, int, int)} instead, or the paddingTop
	 * and the paddingBottom may be set incorrectly.
	 */
	@Deprecated
	@Override
	public final void setPadding(int left, int top, int right, int bottom) {
		super.setPadding(left, top, right, bottom);
	}

	/**
	 * Use this method instead of {@link #setPadding(int, int, int, int)} to
	 * automatically set the paddingTop and the paddingBottom correctly.
	 */
	public void setPaddings(int left, int top, int right, int bottom) {
		innerPaddingTop = top;
		innerPaddingBottom = bottom;
		innerPaddingLeft = left;
		innerPaddingRight = right;
		correctPaddings();
	}

	/**
	 * Set paddings to the correct values
	 */
	private void correctPaddings() {
		int buttonsWidthLeft = 0, buttonsWidthRight = 0;
		int buttonsWidth = iconOuterWidth * getButtonsCount();
		if (isRTL()) {
			buttonsWidthLeft = buttonsWidth;
		} else {
			buttonsWidthRight = buttonsWidth;
		}
		super.setPadding(
				innerPaddingLeft + extraPaddingLeft + buttonsWidthLeft,
				innerPaddingTop + extraPaddingTop, innerPaddingRight
						+ extraPaddingRight + buttonsWidthRight,
				innerPaddingBottom + extraPaddingBottom);
	}

	private int getButtonsCount() {
		return isShowClearButton() ? 1 : 0;
	}

	@Override
	protected void onAttachedToWindow() {
		super.onAttachedToWindow();
		if (!firstShown) {
			firstShown = true;
		}
	}

	@Override
	protected void onLayout(boolean changed, int left, int top, int right,
			int bottom) {
		super.onLayout(changed, left, top, right, bottom);
		if (changed) {
			adjustBottomLines();
		}
	}

	/**
	 * @return True, if adjustments were made that require the view to be
	 *         invalidated.
	 */
	private boolean adjustBottomLines() {
		// Bail out if we have a zero width; lines will be adjusted during next
		// layout.
		if (getWidth() == 0) {
			return false;
		}
		int destBottomLines;
		textPaint.setTextSize(bottomTextSize);
		if (tempErrorText != null || helperText != null) {
			Layout.Alignment alignment = (getGravity() & Gravity.RIGHT) == Gravity.RIGHT
					|| isRTL() ? Layout.Alignment.ALIGN_OPPOSITE
					: (getGravity() & Gravity.LEFT) == Gravity.LEFT ? Layout.Alignment.ALIGN_NORMAL
							: Layout.Alignment.ALIGN_CENTER;
			textLayout = new StaticLayout(tempErrorText != null ? tempErrorText
					: helperText, textPaint, getWidth()
					- getBottomTextLeftOffset() - getBottomTextRightOffset()
					- getPaddingLeft() - getPaddingRight(), alignment, 1.0f,
					0.0f, true);
			destBottomLines = Math.max(textLayout.getLineCount(),
					minBottomTextLines);
		} else {
			destBottomLines = minBottomLines;
		}
		if (bottomLines != destBottomLines) {
			getBottomLinesAnimator(destBottomLines).start();
		}
		bottomLines = destBottomLines;
		return true;
	}

	/**
	 * get inner top padding, not the real paddingTop
	 */
	public int getInnerPaddingTop() {
		return innerPaddingTop;
	}

	/**
	 * get inner bottom padding, not the real paddingBottom
	 */
	public int getInnerPaddingBottom() {
		return innerPaddingBottom;
	}

	/**
	 * get inner left padding, not the real paddingLeft
	 */
	public int getInnerPaddingLeft() {
		return innerPaddingLeft;
	}

	/**
	 * get inner right padding, not the real paddingRight
	 */
	public int getInnerPaddingRight() {
		return innerPaddingRight;
	}

	private void initFloatingLabel() {
		// observe the text changing
		addTextChangedListener(new TextWatcher() {
			@Override
			public void beforeTextChanged(CharSequence s, int start, int count,
					int after) {
			}

			@Override
			public void onTextChanged(CharSequence s, int start, int before,
					int count) {
			}

			@Override
			public void afterTextChanged(Editable s) {
				if (floatingLabelEnabled) {
					if (s.length() == 0) {
						if (floatingLabelShown) {
							floatingLabelShown = false;
							getLabelAnimator().reverse();
						}
					} else if (!floatingLabelShown) {
						floatingLabelShown = true;
						getLabelAnimator().start();
					}
				}
			}
		});
		// observe the focus state to animate the floating label's text color
		// appropriately
		innerFocusChangeListener = new OnFocusChangeListener() {
			@Override
			public void onFocusChange(View v, boolean hasFocus) {
				if (floatingLabelEnabled && highlightFloatingLabel) {
					if (hasFocus) {
						getLabelFocusAnimator().start();
					} else {
						getLabelFocusAnimator().reverse();
					}
				}
				if (validateOnFocusLost && !hasFocus) {
					validate();
				}
				if (outerFocusChangeListener != null) {
					outerFocusChangeListener.onFocusChange(v, hasFocus);
				}
			}
		};
		super.setOnFocusChangeListener(innerFocusChangeListener);
	}

	public boolean isValidateOnFocusLost() {
		return validateOnFocusLost;
	}

	public void setValidateOnFocusLost(boolean validate) {
		this.validateOnFocusLost = validate;
	}

	public void setBaseColor(int color) {
		if (baseColor != color) {
			baseColor = color;
		}

		initText();

		postInvalidate();
	}

	public void setPrimaryColor(int color) {
		primaryColor = color;
		postInvalidate();
	}

	/**
	 * Same function as {@link #setTextColor(int)}. (Directly overriding the
	 * built-in one could cause some error, so use this method instead.)
	 */
	public void setMetTextColor(int color) {
		textColorStateList = ColorStateList.valueOf(color);
		resetTextColor();
	}

	/**
	 * Same function as {@link #setTextColor(ColorStateList)}. (Directly
	 * overriding the built-in one could cause some error, so use this method
	 * instead.)
	 */
	public void setMetTextColor(ColorStateList colors) {
		textColorStateList = colors;
		resetTextColor();
	}

	private void resetTextColor() {
		if (textColorStateList == null) {
			textColorStateList = new ColorStateList(
					new int[][] { new int[] { android.R.attr.state_enabled },
							EMPTY_STATE_SET }, new int[] {
							baseColor & 0x00ffffff | 0xdf000000,
							baseColor & 0x00ffffff | 0x44000000 });
			setTextColor(textColorStateList);
		} else {
			setTextColor(textColorStateList);
		}
	}

	/**
	 * Same function as {@link #setHintTextColor(int)}. (The built-in one is a
	 * final method that can't be overridden, so use this method instead.)
	 */
	public void setMetHintTextColor(int color) {
		textColorHintStateList = ColorStateList.valueOf(color);
		resetHintTextColor();
	}

	/**
	 * Same function as {@link #setHintTextColor(ColorStateList)}. (The built-in
	 * one is a final method that can't be overridden, so use this method
	 * instead.)
	 */
	public void setMetHintTextColor(ColorStateList colors) {
		textColorHintStateList = colors;
		resetHintTextColor();
	}

	private void resetHintTextColor() {
		if (textColorHintStateList == null) {
			setHintTextColor(baseColor & 0x00ffffff | 0x44000000);
		} else {
			setHintTextColor(textColorHintStateList);
		}
	}

	private void setFloatingLabelInternal(int mode) {
		switch (mode) {
		case FLOATING_LABEL_NORMAL:
			floatingLabelEnabled = true;
			highlightFloatingLabel = false;
			break;
		case FLOATING_LABEL_HIGHLIGHT:
			floatingLabelEnabled = true;
			highlightFloatingLabel = true;
			break;
		default:
			floatingLabelEnabled = false;
			highlightFloatingLabel = false;
			break;
		}
	}

	public void setFloatingLabel(@FloatingLabelType int mode) {
		setFloatingLabelInternal(mode);
		initPadding();
	}

	public int getFloatingLabelPadding() {
		return floatingLabelPadding;
	}

	public void setFloatingLabelPadding(int padding) {
		floatingLabelPadding = padding;
		postInvalidate();
	}

	public boolean isFloatingLabelAnimating() {
		return floatingLabelAnimating;
	}

	public void setFloatingLabelAnimating(boolean animating) {
		floatingLabelAnimating = animating;
	}

	public void setSingleLineEllipsis() {
		setSingleLineEllipsis(true);
	}

	public void setSingleLineEllipsis(boolean enabled) {
		singleLineEllipsis = enabled;
		initMinBottomLines();
		initPadding();
		postInvalidate();
	}

	public int getMaxCharacters() {
		return maxCharacters;
	}

	public void setMaxCharacters(int max) {
		maxCharacters = max;
		initMinBottomLines();
		initPadding();
		postInvalidate();
	}

	public int getMinCharacters() {
		return minCharacters;
	}

	public void setMinCharacters(int min) {
		minCharacters = min;
		initMinBottomLines();
		initPadding();
		postInvalidate();
	}

	public int getMinBottomTextLines() {
		return minBottomTextLines;
	}

	public void setMinBottomTextLines(int lines) {
		minBottomTextLines = lines;
		initMinBottomLines();
		initPadding();
		postInvalidate();
	}

	public boolean isAutoValidate() {
		return autoValidate;
	}

	public void setAutoValidate(boolean autoValidate) {
		this.autoValidate = autoValidate;
		if (autoValidate) {
			validate();
		}
	}

	public int getErrorColor() {
		return errorColor;
	}

	public void setErrorColor(int color) {
		errorColor = color;
		postInvalidate();
	}

	public void setHelperText(CharSequence helperText) {
		this.helperText = helperText == null ? null : helperText.toString();
		if (adjustBottomLines()) {
			postInvalidate();
		}
	}

	public String getHelperText() {
		return helperText;
	}

	public int getHelperTextColor() {
		return helperTextColor;
	}

	public void setHelperTextColor(int color) {
		helperTextColor = color;
		postInvalidate();
	}

	@Override
	public void setError(CharSequence errorText) {
		tempErrorText = errorText == null ? null : errorText.toString();
		if (adjustBottomLines()) {
			postInvalidate();
		}
	}

	@Override
	public CharSequence getError() {
		return tempErrorText;
	}

	/**
	 * only used to draw the bottom line
	 */
	private boolean isInternalValid() {
		return tempErrorText == null && isCharactersCountValid();
	}

	/**
	 * if the main text matches the regex
	 * 
	 * @deprecated use the new validator interface to add your own custom
	 *             validator
	 */
	@Deprecated
	public boolean isValid(String regex) {
		if (regex == null) {
			return false;
		}
		Pattern pattern = Pattern.compile(regex);
		Matcher matcher = pattern.matcher(getText());
		return matcher.matches();
	}

	/**
	 * check if the main text matches the regex, and set the error text if not.
	 * 
	 * @return true if it matches the regex, false if not.
	 * @deprecated use the new validator interface to add your own custom
	 *             validator
	 */
	@Deprecated
	public boolean validate(String regex, CharSequence errorText) {
		boolean isValid = isValid(regex);
		if (!isValid) {
			setError(errorText);
		}
		postInvalidate();
		return isValid;
	}

	/**
	 * Run validation on a single validator instance
	 * 
	 * @param validator
	 *            Validator to check
	 * @return True if valid, false if not
	 */
	public boolean validateWith(@NonNull METValidator validator) {
		CharSequence text = getText();
		boolean isValid = validator.isValid(text, text.length() == 0);
		if (!isValid) {
			setError(validator.getErrorMessage());
		}
		postInvalidate();
		return isValid;
	}

	/**
	 * Check all validators, sets the error text if not
	 * <p/>
	 * NOTE: this stops at the first validator to report invalid.
	 * 
	 * @return True if all validators pass, false if not
	 */
	public boolean validate() {
		if (validators == null || validators.isEmpty()) {
			return true;
		}

		CharSequence text = getText();
		boolean isEmpty = text.length() == 0;

		boolean isValid = true;
		for (METValidator validator : validators) {
			// noinspection ConstantConditions
			isValid = isValid && validator.isValid(text, isEmpty);
			if (!isValid) {
				setError(validator.getErrorMessage());
				break;
			}
		}
		if (isValid) {
			setError(null);
		}

		postInvalidate();
		return isValid;
	}

	public boolean hasValidators() {
		return this.validators != null && !this.validators.isEmpty();
	}

	/**
	 * Adds a new validator to the View's list of validators
	 * <p/>
	 * This will be checked with the others in {@link #validate()}
	 * 
	 * @param validator
	 *            Validator to add
	 * @return This instance, for easy chaining
	 */
	public MaterialMultiAutoCompleteTextView addValidator(METValidator validator) {
		if (validators == null) {
			this.validators = new ArrayList<METValidator>();
		}
		this.validators.add(validator);
		return this;
	}

	public void clearValidators() {
		if (this.validators != null) {
			this.validators.clear();
		}
	}

	@Nullable
	public List<METValidator> getValidators() {
		return this.validators;
	}

	public void setLengthChecker(METLengthChecker lengthChecker) {
		this.lengthChecker = lengthChecker;
	}

	@Override
	public void setOnFocusChangeListener(OnFocusChangeListener listener) {
		if (innerFocusChangeListener == null) {
			super.setOnFocusChangeListener(listener);
		} else {
			outerFocusChangeListener = listener;
		}
	}

	private ObjectAnimator getLabelAnimator() {
		if (labelAnimator == null) {
			labelAnimator = ObjectAnimator.ofFloat(this,
					"floatingLabelFraction", 0f, 1f);
		}
		labelAnimator.setDuration(floatingLabelAnimating ? 300 : 0);
		return labelAnimator;
	}

	private ObjectAnimator getLabelFocusAnimator() {
		if (labelFocusAnimator == null) {
			labelFocusAnimator = ObjectAnimator.ofFloat(this, "focusFraction",
					0f, 1f);
		}
		return labelFocusAnimator;
	}

	private ObjectAnimator getBottomLinesAnimator(float destBottomLines) {
		if (bottomLinesAnimator == null) {
			bottomLinesAnimator = ObjectAnimator.ofFloat(this,
					"currentBottomLines", destBottomLines);
		} else {
			bottomLinesAnimator.cancel();
			bottomLinesAnimator.setFloatValues(destBottomLines);
		}
		return bottomLinesAnimator;
	}

	@Override
	protected void onDraw(@NonNull Canvas canvas) {
		int startX = getScrollX()
				+ (iconLeftBitmaps == null ? 0 : (iconOuterWidth + iconPadding));
		int endX = getScrollX()
				+ (iconRightBitmaps == null ? getWidth() : getWidth()
						- iconOuterWidth - iconPadding);
		int lineStartY = getScrollY() + getHeight() - getPaddingBottom();

		// draw the icon(s)
		paint.setAlpha(255);
		if (iconLeftBitmaps != null) {
			Bitmap icon = iconLeftBitmaps[!isInternalValid() ? 3
					: !isEnabled() ? 2 : hasFocus() ? 1 : 0];
			int iconLeft = startX - iconPadding - iconOuterWidth
					+ (iconOuterWidth - icon.getWidth()) / 2;
			int iconTop = lineStartY + bottomSpacing - iconOuterHeight
					+ (iconOuterHeight - icon.getHeight()) / 2;
			canvas.drawBitmap(icon, iconLeft, iconTop, paint);
		}
		if (iconRightBitmaps != null) {
			Bitmap icon = iconRightBitmaps[!isInternalValid() ? 3
					: !isEnabled() ? 2 : hasFocus() ? 1 : 0];
			int iconRight = endX + iconPadding
					+ (iconOuterWidth - icon.getWidth()) / 2;
			int iconTop = lineStartY + bottomSpacing - iconOuterHeight
					+ (iconOuterHeight - icon.getHeight()) / 2;
			canvas.drawBitmap(icon, iconRight, iconTop, paint);
		}

		// draw the clear button
		if (hasFocus() && showClearButton && !TextUtils.isEmpty(getText())) {
			paint.setAlpha(255);
			int buttonLeft;
			if (isRTL()) {
				buttonLeft = startX;
			} else {
				buttonLeft = endX - iconOuterWidth;
			}
			Bitmap clearButtonBitmap = clearButtonBitmaps[0];
			buttonLeft += (iconOuterWidth - clearButtonBitmap.getWidth()) / 2;
			int iconTop = lineStartY + bottomSpacing - iconOuterHeight
					+ (iconOuterHeight - clearButtonBitmap.getHeight()) / 2;
			canvas.drawBitmap(clearButtonBitmap, buttonLeft, iconTop, paint);
		}

		// draw the underline
		if (!hideUnderline) {
			lineStartY += bottomSpacing;
			if (!isInternalValid()) { // not valid
				paint.setColor(errorColor);
				canvas.drawRect(startX, lineStartY, endX, lineStartY
						+ getPixel(2), paint);
			} else if (!isEnabled()) { // disabled
				paint.setColor(underlineColor != -1 ? underlineColor
						: baseColor & 0x00ffffff | 0x44000000);
				float interval = getPixel(1);
				for (float xOffset = 0; xOffset < getWidth(); xOffset += interval * 3) {
					canvas.drawRect(startX + xOffset, lineStartY, startX
							+ xOffset + interval, lineStartY + getPixel(1),
							paint);
				}
			} else if (hasFocus()) { // focused
				paint.setColor(primaryColor);
				canvas.drawRect(startX, lineStartY, endX, lineStartY
						+ getPixel(2), paint);
			} else { // normal
				paint.setColor(underlineColor != -1 ? underlineColor
						: baseColor & 0x00ffffff | 0x1E000000);
				canvas.drawRect(startX, lineStartY, endX, lineStartY
						+ getPixel(1), paint);
			}
		}

		textPaint.setTextSize(bottomTextSize);
		Paint.FontMetrics textMetrics = textPaint.getFontMetrics();
		float relativeHeight = -textMetrics.ascent - textMetrics.descent;
		float bottomTextPadding = bottomTextSize + textMetrics.ascent
				+ textMetrics.descent;

		// draw the characters counter
		if ((hasFocus() && hasCharactersCounter()) || !isCharactersCountValid()) {
			textPaint
					.setColor(isCharactersCountValid() ? (baseColor & 0x00ffffff | 0x44000000)
							: errorColor);
			String charactersCounterText = getCharactersCounterText();
			canvas.drawText(charactersCounterText, isRTL() ? startX : endX
					- textPaint.measureText(charactersCounterText), lineStartY
					+ bottomSpacing + relativeHeight, textPaint);
		}

		// draw the bottom text
		if (textLayout != null) {
			if (tempErrorText != null
					|| ((helperTextAlwaysShown || hasFocus()) && !TextUtils
							.isEmpty(helperText))) { // error text or helper
														// text
				textPaint.setColor(tempErrorText != null ? errorColor
						: helperTextColor != -1 ? helperTextColor
								: (baseColor & 0x00ffffff | 0x44000000));
				canvas.save();
				if (isRTL()) {
					canvas.translate(endX - textLayout.getWidth(), lineStartY
							+ bottomSpacing - bottomTextPadding);
				} else {
					canvas.translate(startX + getBottomTextLeftOffset(),
							lineStartY + bottomSpacing - bottomTextPadding);
				}
				textLayout.draw(canvas);
				canvas.restore();
			}
		}

		// draw the floating label
		if (floatingLabelEnabled && !TextUtils.isEmpty(floatingLabelText)) {
			textPaint.setTextSize(floatingLabelTextSize);
			// calculate the text color
			textPaint.setColor((Integer) focusEvaluator.evaluate(focusFraction,
					floatingLabelTextColor != -1 ? floatingLabelTextColor
							: (baseColor & 0x00ffffff | 0x44000000),
					primaryColor));

			// calculate the horizontal position
			float floatingLabelWidth = textPaint.measureText(floatingLabelText
					.toString());
			int floatingLabelStartX;
			if ((getGravity() & Gravity.RIGHT) == Gravity.RIGHT || isRTL()) {
				floatingLabelStartX = (int) (endX - floatingLabelWidth);
			} else if ((getGravity() & Gravity.LEFT) == Gravity.LEFT) {
				floatingLabelStartX = startX;
			} else {
				floatingLabelStartX = startX
						+ (int) (getInnerPaddingLeft() + (getWidth()
								- getInnerPaddingLeft()
								- getInnerPaddingRight() - floatingLabelWidth) / 2);
			}

			// calculate the vertical position
			int distance = floatingLabelPadding;
			int floatingLabelStartY = (int) (innerPaddingTop
					+ floatingLabelTextSize + floatingLabelPadding - distance
					* (floatingLabelAlwaysShown ? 1 : floatingLabelFraction) + getScrollY());

			// calculate the alpha
			int alpha = ((int) ((floatingLabelAlwaysShown ? 1
					: floatingLabelFraction)
					* 0xff
					* (0.74f * focusFraction + 0.26f) * (floatingLabelTextColor != -1 ? 1
					: Color.alpha(floatingLabelTextColor) / 256f)));
			textPaint.setAlpha(alpha);

			// draw the floating label
			canvas.drawText(floatingLabelText.toString(), floatingLabelStartX,
					floatingLabelStartY, textPaint);
		}

		// draw the bottom ellipsis
		if (hasFocus() && singleLineEllipsis && getScrollX() != 0) {
			paint.setColor(isInternalValid() ? primaryColor : errorColor);
			float startY = lineStartY + bottomSpacing;
			int ellipsisStartX;
			if (isRTL()) {
				ellipsisStartX = endX;
			} else {
				ellipsisStartX = startX;
			}
			int signum = isRTL() ? -1 : 1;
			canvas.drawCircle(ellipsisStartX + signum * bottomEllipsisSize / 2,
					startY + bottomEllipsisSize / 2, bottomEllipsisSize / 2,
					paint);
			canvas.drawCircle(ellipsisStartX + signum * bottomEllipsisSize * 5
					/ 2, startY + bottomEllipsisSize / 2,
					bottomEllipsisSize / 2, paint);
			canvas.drawCircle(ellipsisStartX + signum * bottomEllipsisSize * 9
					/ 2, startY + bottomEllipsisSize / 2,
					bottomEllipsisSize / 2, paint);
		}

		// draw the original things
		super.onDraw(canvas);
	}

	@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
	private boolean isRTL() {
		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
			return false;
		}
		Configuration config = getResources().getConfiguration();
		return config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
	}

	private int getBottomTextLeftOffset() {
		return isRTL() ? getCharactersCounterWidth() : getBottomEllipsisWidth();
	}

	private int getBottomTextRightOffset() {
		return isRTL() ? getBottomEllipsisWidth() : getCharactersCounterWidth();
	}

	private int getCharactersCounterWidth() {
		return hasCharactersCounter() ? (int) textPaint
				.measureText(getCharactersCounterText()) : 0;
	}

	private int getBottomEllipsisWidth() {
		return singleLineEllipsis ? (bottomEllipsisSize * 5 + getPixel(4)) : 0;
	}

	private void checkCharactersCount() {
		if ((!firstShown && !checkCharactersCountAtBeginning)
				|| !hasCharactersCounter()) {
			charactersCountValid = true;
		} else {
			CharSequence text = getText();
			int count = text == null ? 0 : checkLength(text);
			charactersCountValid = (count >= minCharacters && (maxCharacters <= 0 || count <= maxCharacters));
		}
	}

	public boolean isCharactersCountValid() {
		return charactersCountValid;
	}

	private boolean hasCharactersCounter() {
		return minCharacters > 0 || maxCharacters > 0;
	}

	private String getCharactersCounterText() {
		String text;
		if (minCharacters <= 0) {
			text = isRTL() ? maxCharacters + " / " + checkLength(getText())
					: checkLength(getText()) + " / " + maxCharacters;
		} else if (maxCharacters <= 0) {
			text = isRTL() ? "+" + minCharacters + " / "
					+ checkLength(getText()) : checkLength(getText()) + " / "
					+ minCharacters + "+";
		} else {
			text = isRTL() ? maxCharacters + "-" + minCharacters + " / "
					+ checkLength(getText()) : checkLength(getText()) + " / "
					+ minCharacters + "-" + maxCharacters;
		}
		return text;
	}

	@SuppressLint("ClickableViewAccessibility")
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (singleLineEllipsis
				&& getScrollX() > 0
				&& event.getAction() == MotionEvent.ACTION_DOWN
				&& event.getX() < getPixel(4 * 5)
				&& event.getY() > getHeight() - extraPaddingBottom
						- innerPaddingBottom
				&& event.getY() < getHeight() - innerPaddingBottom) {
			setSelection(0);
			return false;
		}
		if (hasFocus() && showClearButton) {
			switch (event.getAction()) {
			case MotionEvent.ACTION_DOWN:
				if (insideClearButton(event)) {
					clearButtonTouched = true;
					clearButtonClicking = true;
					return true;
				}
			case MotionEvent.ACTION_MOVE:
				if (clearButtonClicking && !insideClearButton(event)) {
					clearButtonClicking = false;
				}
				if (clearButtonTouched) {
					return true;
				}
				break;
			case MotionEvent.ACTION_UP:
				if (clearButtonClicking) {
					if (!TextUtils.isEmpty(getText())) {
						setText(null);
					}
					clearButtonClicking = false;
				}
				if (clearButtonTouched) {
					clearButtonTouched = false;
					return true;
				}
				clearButtonTouched = false;
				break;
			case MotionEvent.ACTION_CANCEL:
				clearButtonTouched = false;
				clearButtonClicking = false;
				break;
			}
		}
		return super.onTouchEvent(event);
	}

	private boolean insideClearButton(MotionEvent event) {
		float x = event.getX();
		float y = event.getY();
		int startX = getScrollX()
				+ (iconLeftBitmaps == null ? 0 : (iconOuterWidth + iconPadding));
		int endX = getScrollX()
				+ (iconRightBitmaps == null ? getWidth() : getWidth()
						- iconOuterWidth - iconPadding);
		int buttonLeft;
		if (isRTL()) {
			buttonLeft = startX;
		} else {
			buttonLeft = endX - iconOuterWidth;
		}
		int buttonTop = getScrollY() + getHeight() - getPaddingBottom()
				+ bottomSpacing - iconOuterHeight;
		return (x >= buttonLeft && x < buttonLeft + iconOuterWidth
				&& y >= buttonTop && y < buttonTop + iconOuterHeight);
	}

	private int checkLength(CharSequence text) {
		if (lengthChecker == null)
			return text.length();
		return lengthChecker.getLength(text);
	}
}